/*
 * Copyright 2010 Gal Dolber.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.unnison.framework.rebind.jsorm;

import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONBoolean;
import com.google.gwt.json.client.JSONNull;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import com.unnison.framework.client.jsorm.BooleanSerializer;
import com.unnison.framework.client.jsorm.DateSerializer;
import com.unnison.framework.client.jsorm.DoubleSerializer;
import com.unnison.framework.client.jsorm.FloatSerializer;
import com.unnison.framework.client.jsorm.IntegerSerializer;
import com.unnison.framework.client.jsorm.LongSerializer;
import com.unnison.framework.client.jsorm.StringSerializer;
import com.unnison.framework.client.jsorm.TypeJsonSerializer;
import com.unnison.framework.client.jsorm.VoidSerializer;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Json serializer generator. Support virtually any kind of serializable data but java.lang.Object. For Iterable
 * Interfaces like java.util.Set, java.util.List or java.util.Map you need to add an exception to specify what
 * implementation to use. java.util.List, java.util.Set, java.util.Map are already supported. See Framework.gwt.xml.
 */
public class JsonSerializerUtil {

    private static final JType[] emptyParameter = new JType[] {};
    private static final String jsonObject = JSONObject.class.getCanonicalName();
    private static TreeLogger logger;
    private static String packageName = "com.unnison.framework.client.jsorm";
    private static HashMap<String, String> exceptions;

    private static void error(String message, Object... params) throws UnableToCompleteException {
        logger.log(TreeLogger.ERROR, String.format(message, params));
        throw new UnableToCompleteException();
    }

    public static String generate(TreeLogger logger, GeneratorContext context, JClassType pojoType) throws UnableToCompleteException {
        JsonSerializerUtil.logger = logger;

        // We cannot serialize java.lang.Object
        if (pojoType.getQualifiedSourceName().equals(Object.class.getCanonicalName())) {
            error("You cannot serialize Object... we either");
        }

        if (exceptions == null) {
            exceptions = new HashMap<String, String>();
            try {
                List<String> ormExceptions = context.getPropertyOracle().getConfigurationProperty("json.orm.exception").getValues();
                for (String e : ormExceptions) {
                    String[] parts = e.split(" ");
                    if (parts.length != 2) {
                        error("Bad json orm exception format. i.e 'java.util.List java.util.ArrayList<%%s>. Found: %s'", e);
                    }
                    exceptions.put(parts[0], parts[1]);
                }
            } catch (BadPropertyValueException e) {
                throw new IllegalStateException(e);
            }
        }

        String parameterizedQualifiedSourceName = pojoType.getParameterizedQualifiedSourceName();
        String typeName = parameterizedQualifiedSourceName;

        // Basic types
        if (typeName.equals(Void.class.getCanonicalName())) {
            return VoidSerializer.class.getCanonicalName();
        } else if (typeName.equals(String.class.getCanonicalName())) {
            return StringSerializer.class.getCanonicalName();
        } else if (typeName.equals(Integer.class.getCanonicalName())) {
            return IntegerSerializer.class.getCanonicalName();
        } else if (typeName.equals(Long.class.getCanonicalName())) {
            return LongSerializer.class.getCanonicalName();
        } else if (typeName.equals(Double.class.getCanonicalName())) {
            return DoubleSerializer.class.getCanonicalName();
        } else if (typeName.equals(Float.class.getCanonicalName())) {
            return FloatSerializer.class.getCanonicalName();
        } else if (typeName.equals(Date.class.getCanonicalName())) {
            return DateSerializer.class.getCanonicalName();
        } else if (typeName.equals(Boolean.class.getCanonicalName())) {
            return BooleanSerializer.class.getCanonicalName();
        }

        // Build name avoiding generics collitions
        StringBuilder implName = new StringBuilder();
        makeImplName(pojoType, implName);
        implName.append("Serializer");

        String implNameString = implName.toString();
        ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(packageName, implNameString);
        composer.addImplementedInterface(TypeJsonSerializer.class.getCanonicalName() + "<" + typeName + ">");
        PrintWriter printWriter = context.tryCreate(logger, packageName, implNameString);
        String createdName = composer.getCreatedClassName();
        if (printWriter != null) {
            SourceWriter writer = composer.createSourceWriter(context, printWriter);

            JType iterableParameterType = null;
            JPrimitiveType iterableParameterPrimitiveType = null;

            // Iterable
            JGenericType iterableType = context.getTypeOracle().findType(Iterable.class.getCanonicalName()).isGenericType();
            boolean isIterable = false;
            if (iterableType.isAssignableFrom(pojoType)) {
                isIterable = true;
                iterableParameterType = pojoType.asParameterizationOf(iterableType).getTypeArgs()[0];
                iterableParameterPrimitiveType = iterableParameterType.isPrimitive();

                // Find if theres any exception
                String qualifiedSourceName = pojoType.getQualifiedSourceName();
                if (exceptions.containsKey(qualifiedSourceName)) {
                    parameterizedQualifiedSourceName = exceptions.get(qualifiedSourceName) + "<"
                        + iterableParameterType.getParameterizedQualifiedSourceName() + ">";
                }
            }

            // Map
            JGenericType mapType = context.getTypeOracle().findType(Map.class.getCanonicalName()).isGenericType();
            boolean isMap = false;
            JClassType mapKeyType = null;
            JClassType mapValueType = null;
            if (mapType.isAssignableFrom(pojoType)) {
                isMap = true;
                JParameterizedType pojoMap = pojoType.asParameterizationOf(mapType);
                JClassType[] args = pojoMap.getTypeArgs();
                mapKeyType = args[0];
                mapValueType = args[1];

                // Find if theres any exception
                String qualifiedSourceName = pojoType.getQualifiedSourceName();
                if (exceptions.containsKey(qualifiedSourceName)) {
                    parameterizedQualifiedSourceName = exceptions.get(qualifiedSourceName) + "<"
                        + mapKeyType.getParameterizedQualifiedSourceName() + "," + mapValueType.getParameterizedQualifiedSourceName() + ">";
                }
            }

            // Array
            boolean isArray = false;
            JArrayType pojoArray = pojoType.isArray();
            if (pojoArray != null) {
                isArray = true;
                iterableParameterType = pojoArray.getComponentType();
                iterableParameterPrimitiveType = iterableParameterType.isPrimitive();
            }

            ArrayList<JField> fields = null; // For only for pojo

            writer.println("public static " + createdName + " singleton;");

            writer.println("public static " + createdName + " getSingleton() {");
            writer.indent();
            writer.println("return singleton == null ? (singleton = new " + createdName + "()) : singleton;");
            writer.outdent();
            writer.println("}");

            writer.println("@Override");
            writer.println("public " + JSONValue.class.getCanonicalName() + " serialize(" + typeName + " data) {");
            writer.indent();

            if (isMap) {
                writer.println("if (data != null) {");
                writer.indent();
                writer.println(JSONArray.class.getCanonicalName() + " array = new " + JSONArray.class.getCanonicalName() + "();");
                writer.println("int n = 0;");
                writer.println("for (" + Entry.class.getCanonicalName() + "<" + mapKeyType.getParameterizedQualifiedSourceName() + ", "
                    + mapValueType.getParameterizedQualifiedSourceName() + ">" + " entry : data.entrySet()) {");
                writer.indent();

                writer.print("array.set(n, ");
                JPrimitiveType mapKeyPrimitive = mapKeyType.isPrimitive();
                if (mapKeyPrimitive == null) {
                    printValueSerialized(logger, context, writer, "entry.getKey()", mapKeyType, pojoType);
                } else {
                    printPrimitiveSerialized(typeName, writer, "entry.getKey()", mapKeyPrimitive);
                }
                writer.println(");");
                writer.println("n++;");

                writer.print("array.set(n, ");
                JPrimitiveType mapValuePrimitive = mapValueType.isPrimitive();
                if (mapValuePrimitive == null) {
                    printValueSerialized(logger, context, writer, "entry.getValue()", mapValueType, pojoType);
                } else {
                    printPrimitiveSerialized(typeName, writer, "entry.getValue()", mapValuePrimitive);
                }
                writer.println(");");
                writer.println("n++;");

                writer.outdent();
                writer.println("}");
                writer.println("return array;");
                writer.outdent();
                writer.println("}");
                writer.println("return " + JSONNull.class.getCanonicalName() + ".getInstance();");
            } else if (isIterable || isArray) {
                writer.println("if (data != null) {");
                writer.indent();
                writer.println(JSONArray.class.getCanonicalName() + " array = new " + JSONArray.class.getCanonicalName() + "();");
                writer.println("int n = 0;");
                writer.println("for (" + iterableParameterType.getParameterizedQualifiedSourceName() + " item : data) {");
                writer.indent();

                writer.print("array.set(n, ");
                if (iterableParameterPrimitiveType == null) {
                    printValueSerialized(logger, context, writer, "item", iterableParameterType, pojoType);
                } else {
                    printPrimitiveSerialized(typeName, writer, "item", iterableParameterPrimitiveType);
                }
                writer.println(");");
                writer.println("n++;");

                writer.outdent();
                writer.println("}");
                writer.println("return array;");
                writer.outdent();
                writer.println("}");
                writer.println("return " + JSONNull.class.getCanonicalName() + ".getInstance();");
            } else if (pojoType.isEnum() != null) {
                writer.println("if (data != null) {");
                writer.indent();
                writer.println("return new " + JSONString.class.getCanonicalName() + "(data.name());");
                writer.outdent();
                writer.println("}");
                writer.println("return " + JSONNull.class.getCanonicalName() + ".getInstance();");
            } else {
                // Assert the type have an empty constructor
                try {
                    pojoType.getConstructor(emptyParameter);
                } catch (NotFoundException e) {
                    error("The data type of the place does not have an empty constructor. Found %s", typeName);
                }

                writer.println(jsonObject + " json = new " + jsonObject + "();");

                fields = new ArrayList<JField>();
                getFields(fields, pojoType);
                for (JField f : fields) {
                    String fieldName = f.getName();
                    String getterName = fieldName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                    JType fieldType = f.getType();
                    JPrimitiveType primitive = fieldType.isPrimitive();
                    if (primitive != null) {
                        writer.print("json.put(\"" + fieldName + "\",");
                        printPrimitiveSerialized(typeName, writer, "data." + (primitive.equals(JPrimitiveType.BOOLEAN) ? "is" : "get")
                            + getterName + "()", primitive);
                        writer.println(");");
                    } else {
                        writer.println(fieldType.getQualifiedSourceName() + " " + fieldName + " = data.get" + getterName + "();");
                        writer.println("if (" + fieldName + " != null) {");
                        writer.indent();

                        writer.print("json.put(\"" + fieldName + "\",");
                        printValueSerialized(logger, context, writer, fieldName, fieldType, pojoType);
                        writer.println(");");

                        writer.outdent();
                        writer.println("}");
                    }
                }

                writer.println("return json;");
            }

            writer.outdent();
            writer.println("}");

            writer.println("@Override");
            writer.println("public " + typeName + " deserialize(" + JSONValue.class.getCanonicalName() + " jsonValue) {");
            writer.indent();

            if (isMap) {
                writer.println("if (jsonValue.isNull() == null) {");
                writer.indent();

                writer.println(JSONArray.class.getCanonicalName() + " jsonArray = jsonValue.isArray();");
                writer.println("int jsonArraySize = jsonArray.size();");

                writer.println(parameterizedQualifiedSourceName + " map = new " + parameterizedQualifiedSourceName + "();");

                writer.println("for (int n = 0; n < jsonArraySize/2; n+=2) {");
                writer.indent();
                writer.println(JSONValue.class.getCanonicalName() + " key = jsonArray.get(n);");
                writer.println(JSONValue.class.getCanonicalName() + " value = jsonArray.get(n + 1);");

                writer.print("map.put(");
                JPrimitiveType mapKeyPrimitive = mapKeyType.isPrimitive();
                if (mapKeyPrimitive == null) {
                    printValueDeserialized(logger, context, writer, "key", mapKeyType);
                } else {
                    printPrimitiveDeserialized(typeName, writer, "key", mapKeyPrimitive);
                }
                writer.print(",");
                JPrimitiveType mapValuePrimitive = mapValueType.isPrimitive();
                if (mapValuePrimitive == null) {
                    printValueDeserialized(logger, context, writer, "value", mapValueType);
                } else {
                    printPrimitiveDeserialized(typeName, writer, "value", mapValuePrimitive);
                }
                writer.println(");");

                writer.outdent();
                writer.println("}");
                writer.println("return map;");
                writer.outdent();
                writer.println("} else { return null; }");
            } else if (isIterable || isArray) {
                writer.println("if (jsonValue.isNull() == null) {");
                writer.indent();

                writer.println(JSONArray.class.getCanonicalName() + " jsonArray = jsonValue.isArray();");
                writer.println("int jsonArraySize = jsonArray.size();");

                if (isIterable) {
                    writer.println(parameterizedQualifiedSourceName + " array = new " + parameterizedQualifiedSourceName + "();");
                } else {
                    JArrayType array = iterableParameterType.isArray();
                    if (array != null) {
                        String arrayName = array.getQualifiedSourceName() + "[]";
                        int index = arrayName.indexOf("[");
                        String arrayDeclaration = arrayName.substring(0, index + 1) + "jsonArraySize" + arrayName.substring(index + 1);
                        writer.println(arrayName + " array = new " + arrayDeclaration + ";");
                    } else {
                        String parameterQualifiedName = iterableParameterType.getQualifiedSourceName();
                        writer.println(parameterQualifiedName + "[] array = new " + parameterQualifiedName + "[jsonArraySize];");
                    }
                }

                writer.println("for (int n = 0; n < jsonArraySize; n++) {");
                writer.indent();
                writer.println(JSONValue.class.getCanonicalName() + " item = jsonArray.get(n);");

                if (isIterable) {
                    writer.print("array.add(");
                } else {
                    writer.print("array[n] = ");
                }

                if (iterableParameterPrimitiveType == null) {
                    printValueDeserialized(logger, context, writer, "item", iterableParameterType);
                } else {
                    printPrimitiveDeserialized(typeName, writer, "item", iterableParameterPrimitiveType);
                }

                if (isIterable) {
                    writer.println(");");
                } else {
                    writer.println(";");
                }

                writer.outdent();
                writer.println("}");
                writer.println("return array;");
                writer.outdent();
                writer.println("} else { return null; }");
            } else if (pojoType.isEnum() != null) {
                writer.println("if (jsonValue.isNull() == null) {");
                writer.indent();
                writer.println("return " + typeName + ".valueOf(jsonValue.isString().stringValue());");
                writer.outdent();
                writer.println("} else { return null; }");

            } else {
                // Assert the type have an empty constructor
                try {
                    pojoType.getConstructor(emptyParameter);
                } catch (NotFoundException e) {
                    error("The data type of the place does not have an empty constructor. Found %s", typeName);
                }

                writer.println(JSONObject.class.getCanonicalName() + " json = jsonValue.isObject();");
                writer.println(typeName + " instance = new " + typeName + "();");

                for (JField f : fields) {
                    String fieldName = f.getName();
                    String setterName = fieldName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                    JType fieldType = f.getType();
                    JPrimitiveType primitive = fieldType.isPrimitive();
                    if (primitive != null) {
                        writer.print("instance.set" + setterName + "(");
                        printPrimitiveDeserialized(typeName, writer, "json.get(\"" + fieldName + "\")", primitive);
                        writer.println(");");
                    } else {
                        writer.println("if (json.containsKey(\"" + fieldName + "\")) {");
                        writer.indent();

                        writer.print("instance.set" + setterName + "(");
                        printValueDeserialized(logger, context, writer, "json.get(\"" + fieldName + "\")", fieldType);
                        writer.println(");");

                        writer.outdent();
                        writer.println("}");
                    }
                }

                writer.println("return instance;");
            }

            writer.outdent();
            writer.println("}");

            writer.commit(logger);
        }

        return createdName;
    }

    private static void getFields(List<JField> fields, JClassType pojoType) {
        for (JField f : pojoType.getFields()) {
            // Non static, final or transient
            if (f.isStatic() || f.isFinal() || f.isTransient()) {
                continue;
            }

            String fieldName = f.getName();
            fieldName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

            JType fieldType = f.getType();
            if (!haveGetterAndSetter(pojoType, fieldName, fieldType)) {
                continue;
            }

            fields.add(f);
        }

        JClassType superclass = pojoType.getSuperclass();
        if (superclass != null && !superclass.getQualifiedSourceName().equals(Object.class.getCanonicalName())) {
            getFields(fields, superclass);
        }
    }

    private static boolean haveGetterAndSetter(JClassType pojoType, String fieldName, JType fieldType) {
        try {
            JPrimitiveType primitive = fieldType.isPrimitive();
            if (primitive != null && primitive.equals(JPrimitiveType.BOOLEAN)) {
                pojoType.getMethod("is" + fieldName, emptyParameter);
            } else {
                pojoType.getMethod("get" + fieldName, emptyParameter);
            }
            pojoType.getMethod("set" + fieldName, new JType[] {fieldType});
            return true;
        } catch (NotFoundException e) {
            return false;
        }
    }

    private static void makeImplName(JType pojoType, StringBuilder implName) {
        JArrayType array = pojoType.isArray();
        if (pojoType.isPrimitive() != null) {
            implName.append(pojoType.getSimpleSourceName());
        } else if (array != null) {
            implName.append("Array");
            makeImplName(array.getComponentType(), implName);
        } else {
            JParameterizedType parameterized = pojoType.isParameterized();
            implName.append(pojoType.getSimpleSourceName());
            if (parameterized != null) {
                JClassType[] args = parameterized.getTypeArgs();
                for (JClassType a : args) {
                    makeImplName(a, implName);
                }
            }
        }
    }

    private static void printPrimitiveDeserialized(String typeName, SourceWriter writer, String fieldName, JPrimitiveType primitive)
        throws UnableToCompleteException {
        if (primitive.equals(JPrimitiveType.BOOLEAN)) {
            writer.println(fieldName + ".isBoolean().booleanValue()");
        } else if (primitive.equals(JPrimitiveType.DOUBLE)) {
            writer.println(fieldName + ".isNumber().doubleValue()");
        } else if (primitive.equals(JPrimitiveType.FLOAT)) {
            writer.println("(float)" + fieldName + ".isNumber().doubleValue()");
        } else if (primitive.equals(JPrimitiveType.LONG)) {
            writer.println("(long)" + fieldName + ".isNumber().doubleValue()");
        } else if (primitive.equals(JPrimitiveType.INT)) {
            writer.println("(int)" + fieldName + ".isNumber().doubleValue()");
        } else {
            error("The type %s is not a valid type for the place data. Found %s", primitive.getSimpleSourceName(), typeName);
        }
    }

    private static void printPrimitiveSerialized(String typeName, SourceWriter writer, String fieldName, JPrimitiveType primitive)
        throws UnableToCompleteException {
        if (primitive.equals(JPrimitiveType.BOOLEAN)) {
            writer.print(JSONBoolean.class.getCanonicalName() + ".getInstance(" + fieldName + ")");
        } else if (primitive.equals(JPrimitiveType.DOUBLE)) {
            writer.print("new " + JSONNumber.class.getCanonicalName() + "(" + fieldName + ")");
        } else if (primitive.equals(JPrimitiveType.FLOAT)) {
            writer.print("new " + JSONNumber.class.getCanonicalName() + "(" + fieldName + ")");
        } else if (primitive.equals(JPrimitiveType.LONG)) {
            writer.print("new " + JSONNumber.class.getCanonicalName() + "(" + fieldName + ")");
        } else if (primitive.equals(JPrimitiveType.INT)) {
            writer.print("new " + JSONNumber.class.getCanonicalName() + "(" + fieldName + ")");
        } else {
            error("The type %s is not a valid type for the place data. Found %s", primitive.getSimpleSourceName(), typeName);
        }
    }

    private static void printValueDeserialized(TreeLogger logger, GeneratorContext context, SourceWriter writer, String fieldName,
        JType fieldType) throws UnableToCompleteException {
        String fieldTypeName = fieldType.getQualifiedSourceName();
        if (fieldTypeName.equals(String.class.getCanonicalName())) {
            writer.print(fieldName + ".isString().stringValue()");
        } else if (fieldTypeName.equals(Integer.class.getCanonicalName())) {
            writer.print("(int)" + fieldName + ".isNumber().doubleValue()");
        } else if (fieldTypeName.equals(Long.class.getCanonicalName())) {
            writer.print("(long)" + fieldName + ".isNumber().doubleValue()");
        } else if (fieldTypeName.equals(Double.class.getCanonicalName())) {
            writer.print(fieldName + ".isNumber().doubleValue()");
        } else if (fieldTypeName.equals(Float.class.getCanonicalName())) {
            writer.print("(float)" + fieldName + ".isNumber().doubleValue()");
        } else if (fieldTypeName.equals(Date.class.getCanonicalName())) {
            writer.print("new " + Date.class.getCanonicalName() + "((long)" + fieldName + ".isNumber().doubleValue())");
        } else if (fieldTypeName.equals(Boolean.class.getCanonicalName())) {
            writer.print(fieldName + ".isBoolean().booleanValue()");
        } else {
            JClassType classOrInterface = fieldType.isClassOrInterface();
            writer.print(generate(logger, context, (classOrInterface != null ? classOrInterface : fieldType.isArray()))
                + ".getSingleton().deserialize(" + fieldName + ")");
        }
    }

    private static void printValueSerialized(TreeLogger logger, GeneratorContext context, SourceWriter writer, String fieldName,
        JType fieldType, JClassType pojoType) throws UnableToCompleteException {

        String fieldTypeName = fieldType.getQualifiedSourceName();
        if (fieldTypeName.equals(String.class.getCanonicalName())) {
            writer.print("new " + JSONString.class.getCanonicalName() + "(" + fieldName + ")");
        } else if (fieldTypeName.equals(Integer.class.getCanonicalName())) {
            writer.print("new " + JSONNumber.class.getCanonicalName() + "(" + fieldName + ")");
        } else if (fieldTypeName.equals(Long.class.getCanonicalName())) {
            writer.print("new " + JSONNumber.class.getCanonicalName() + "(" + fieldName + ")");
        } else if (fieldTypeName.equals(Double.class.getCanonicalName())) {
            writer.print("new " + JSONNumber.class.getCanonicalName() + "(" + fieldName + ")");
        } else if (fieldTypeName.equals(Float.class.getCanonicalName())) {
            writer.print("new " + JSONNumber.class.getCanonicalName() + "(" + fieldName + ")");
        } else if (fieldTypeName.equals(Date.class.getCanonicalName())) {
            writer.print("new " + JSONNumber.class.getCanonicalName() + "(" + fieldName + ".getTime())");
        } else if (fieldTypeName.equals(Boolean.class.getCanonicalName())) {
            writer.print(JSONBoolean.class.getCanonicalName() + ".getInstance(" + fieldName + ")");
        } else {
            // We cannot serialize java.lang.Object
            if (fieldType.getQualifiedSourceName().equals(Object.class.getCanonicalName())) {
                error("You cannot serialize Object... we either. Found: %s.%s", pojoType.getQualifiedSourceName(), fieldName);
            }

            JClassType classOrInterface = fieldType.isClassOrInterface();
            writer.print(generate(logger, context, (classOrInterface != null ? classOrInterface : fieldType.isArray()))
                + ".getSingleton().serialize(" + fieldName + ")");
        }
    }
}
